package org.budo.jdbc.dao.impl;

import java.io.Serializable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

import org.budo.jdbc.dao.BudoJdbcDao;
import org.budo.jdbc.dao.ResultSetHandler;
import org.budo.jdbc.dao.resultset.handler.AnnotationEntityListResultSetHandler;
import org.budo.jdbc.dao.resultset.handler.AnnotationEntityResultSetHandler;
import org.budo.jdbc.dao.resultset.handler.IntegerResultSetHandler;
import org.budo.jdbc.dao.resultset.handler.StringResultSetHandler;
import org.budo.jdbc.dao.resultset.handler.TimestampResultSetHandler;
import org.budo.jdbc.dao.statement.named.NamedParameterPreparedStatement;
import org.budo.support.dao.page.Page;
import org.budo.support.javax.sql.util.JdbcUtil;

import lombok.NoArgsConstructor;

/**
 * @author lmw
 */
@NoArgsConstructor
public class BudoJdbcDaoImpl extends AbstractBudoJdbcDao implements BudoJdbcDao {
    public BudoJdbcDaoImpl(DataSource dataSource) {
        this.setDataSource(dataSource);
    }

    @Override
    public <T> T findBySql(Class<T> type, String sql, Object[] parameters) {
        return this.executeQuery(sql, parameters, new AnnotationEntityResultSetHandler<T>(type));
    }

    @Override
    public <T> List<T> listBySql(Class<T> entityType, String sql, Object[] parameters, Page page) {
        return this.executeQuery(sql, parameters, new AnnotationEntityListResultSetHandler<T>(entityType));
    }

    @Override
    public Serializable insert(Class<?> entityType, Object entity) {
        throw new RuntimeException();
    }

    @Override
    public Timestamp findTimestampBySql(String sql, Object[] parameters) {
        return this.executeQuery(sql, parameters, new TimestampResultSetHandler(1));
    }

    @Override
    public Integer findIntegerBySql(String sql, Object[] parameters) {
        return this.executeQuery(sql, parameters, new IntegerResultSetHandler(1));
    }

    @Override
    public String findStringBySql(String sql, Object[] parameters) {
        return this.executeQuery(sql, parameters, new StringResultSetHandler(1));
    }

    @Override
    public Integer findIntegerBySql(String sql, Map<String, Object> parameters) {
        return this.executeQuery(sql, parameters, new IntegerResultSetHandler(1));
    }

    @Override
    public <T> T executeQuery(String sql, Map<String, Object> parameters, ResultSetHandler<T> resultSetHandler) {
        Connection connection = JdbcUtil.getConnection(this.getDataSource());
        NamedParameterPreparedStatement preparedStatement = new NamedParameterPreparedStatement(connection, sql);
        preparedStatement.setParameters(parameters);

        try {
            ResultSet resultSet = preparedStatement.executeQuery();
            Object result = null == resultSetHandler //
                    ? resultSet //
                    : resultSetHandler.handleResultSet(resultSet);
            return (T) result;
        } catch (Throwable e) {
            throw new RuntimeException("#71 sql=" + sql + ", parameters=" + parameters + ", e=" + e, e);
        } finally {
            preparedStatement.close();
            JdbcUtil.close(connection);
        }
    }

    @Override
    public Integer executeUpdate(String sql, Object[] parameters) {
        return JdbcUtil.executeUpdate(this.getDataSource(), sql, parameters);
    }

    @Override
    public <T> T executeQuery(String sql, Object[] parameters, ResultSetHandler<T> resultSetHandler) {
        Connection connection = JdbcUtil.getConnection(this.getDataSource());
        PreparedStatement preparedStatement = JdbcUtil.prepareStatement(connection, sql);
        JdbcUtil.setParameters(preparedStatement, parameters);

        ResultSet resultSet = null;
        try {
            resultSet = preparedStatement.executeQuery();
            return (T) (null == resultSetHandler ? resultSet : resultSetHandler.handleResultSet(resultSet));
        } catch (Throwable e) {
            throw new RuntimeException("#105 sql=" + sql + ", parameters=" + Arrays.toString(parameters) + ", e=" + e, e);
        } finally {
            JdbcUtil.close(resultSet);
            JdbcUtil.close(preparedStatement);
            JdbcUtil.close(connection);
        }
    }

    @Override
    public Integer executeUpdate(String sql, Map<String, Object> parameters) {
        Connection connection = JdbcUtil.getConnection(this.getDataSource());
        NamedParameterPreparedStatement preparedStatement = new NamedParameterPreparedStatement(connection, sql);
        preparedStatement.setParameters(parameters);

        try {
            return preparedStatement.executeUpdate();
        } catch (Throwable e) {
            throw new RuntimeException("#122 sql=" + sql + ", parameters=" + parameters + ", e=" + e, e);
        } finally {
            preparedStatement.close();
            JdbcUtil.close(connection);
        }
    }
}